Fly3D SDK

Guide

Home

Introduction to C++

classes
constructors
destructors
inheritance
virtual functions
operators
templates
public/private/protected

C++ in games

Engine Design and Architecture

Fly3D Software Architecture

Plug-ins
bsp_object virtual functions
Fly3D Front-ends
Collision Detection
Stock and Active object list
Sphere of Influence
Communicating between objects and plug-ins
Camera and Scene Drawing
Text Output
Rendering API


Introduction to C++

This is a brief introduction to C++ intended for those who already possess a good facility with C. Its emphasis is on those aspects of C++ that are useful in games. We start with definitions of new features that are included in C++ then move on to games applications
C has been the programming language used in games for a long time and most games are still written in C because of its speed and portability. With the advent of standard C++ definitions, C++ now possesses all the speed and portability of pure C in addition to those features that can be used to advantage game programming. Thus it is becoming the de facto standard for modern games.
The conventional wisdom is that C++ is slower than C but this will only occur if sufficient care is not taken in the coding.
Those C++ definitions that are useful in game are as follows:

classes

C++ classes are basically C structures with embedded code as member functions. Using classes to specify program objects, enables all the code that deals with the object to be included within it. This is a more organized approach and makes the object blocks easier to manage and re-use.
An example of the conversion of C to C++ representation is:

C

struct vector
{
  float x,y,z;
};
void normalize(struct vector &v)
{
  float len=sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
  if (len>0)
    { v.x/=len; v.y/=len; v.z/=len; }
}

C++

class vector
{
  public:
  float x,y,z;
  void normalize();
};
void vector::normalize()
{
  float len=sqrt(x*x + y*y + z*z);
  if (len>0)
    { x/=len; y/=len; z/=len; }
}

constructors

A constructor is a method called when an instance of the class is allocated. Usually, members are initialized in the constructor. The constructor method must have the same name as the class it is from. The class may have more then one constructor if each of them have different parameters.
Examples of some constructors for the above vector class are:

class vector
{
  public:
  float x,y,z;

  vector()
  { ; }; // uninitialised

  vector(float f1,float f2,float f3)
  { x=f1; y=f2; z=f3; }; // initialised by 3 floats

  vector(float f)
  { x=y=z=f; }; // initialised by 1 float

  vector(vector &v)
  { *this=v; }; // initialised by another vector
};

  ...

{
  vector v1;
  vector v2(0.0f);
  vector v3(0.0f,0.0f,1.0f);
  vector v4(v2);
}

destructors

The destructor is a method called when the class is to be released. Usually, you must check for any data that was allocated by the object, and free it. The destructor is called when the class variable is deleted or gets out of scope. The destructor has the same name as the class, but with a '~' char at the beginning.

class A
{
  public:
  char *data;

  A() { data=0; }
  ~A() { if (data) delete data; };

  void allocate_data(int i)
  {
    if (data) delete data;
    data=new char[i];
  };
};

inheritance

Inheritance is the most important feature of C++ and is the ability t0 create a new class by derivation from one or more already existing classes. All properties and methods of the original classes are maintained and new properties and methods can be included. In this way you can create a base object and use this base code to create new objects that include the base object as a part of it.
For example, a plane can be defined as deriving from a vector (the plane normal) by adding a single float, the plane distance to the origin. This would be:

class plane : public vector
{
  public:
  float d;
  float distance_to(vector &p);
};
float plane::distance_to(vector &p)
{
  // return the distance from point p to the plane
  return x*p.x + y*p.y + z*p.z - d;
}

virtual functions

A virtual function is a member function that you expect to be redefined in derived classes. If you define a function in a class as virtual, any class deriving from it, may have a new implementation for the function. When calling the virtual function from a class pointer (even from a base class pointer) the function that will be executed is the one defined at the allocation time. If your class do not implement the function, the base class version will be used.

class A
{
  A() { printf("A constructor");
  virtual ~A() { printf("A destructor");

  virtual void print() { printf("A print"); };
};
class B : public A
{
  B() { printf("B constructor");
  virtual ~B() { printf("B destructor");
};
class C : public A
{
  C() { printf("C constructor");
  virtual ~C() { printf("C destructor");

  virtual void print() { printf("C print"); };
};

  ...

{
  A *a;
  B b;
  C c;
  a=&b;
  a->print();
  a=&c;
  a->print();
}

will print out:

A constructor
B constructor
A constructor
C constructor
A print
C print
C destructor
A destructor
B destructor
A destructor

operators

When a new class is defined operators must also be defined in order to be used. For example, consider the definition of the operator for the vector class defined above:

inline vector operator +(vector &v1,vector2 &v2)
{
  vector v;
  v.x = v1.x + v2.x;
  v.y = v1.y + v2.y;
  v.z = v1.z + v2.z;
  return v;
}

inline operator *=(vector &v1,float f)
{
  v1.x *= f;
  v1.y *= f;
  v1.z *= f;
}

This would be used in the following context

{
  vector v1,v2(2,2,2),v3(3,3,3);
  float f=0.2f;

  v1=v2+v3;
  v1*=f;
}

and will set v1 to (1,1,1)
The operators that can be redefined in this way are:

+ - ++ -- * / %
^ & | ~ ! < >
= += -= *= /= %= ^=
&= |= << >> >>= <<= ==
!= <= >= && || ->* ->
, () [] new delete new[] delete[]

Care must be taken when using operators like:

+, -, *, /, %, |, &, ^, and ~

as they return values. If the data being operated on is large (a 4x4 matrix, for example), the operators will execute slowly as new copies of temporary data will be created in the process. The following code shows this inefficiency problem.

class A
{
  public:
  int value;
  A(int i=0)
  {
    value=i;
    printf("constructor %i",i);
  };
};
A operator +(A a1,A a2)
{
  return A(a1.value+a2.value);
}

...

{
  A a1(3)
  A a2(4);
  A a3=a1+a2;
}

will print out:

constructor 3
constructor 4
constructor 0
constructor 7

The output æ0Æ is the temporary class allocated by the operator that produces the inefficiency - this will be large for large objects.

templates

Templates are mechanisms for generating functions and classes based on type parameters (templates are sometimes called ôparameterized typesö). By using templates, you can design a single class that operates on data of many types, instead of having to create a separate class for each type.For example, to create a type-safe function that returns the minimum of two parameters without using templates, you would have to write a set of overloaded functions as:

// minimum for ints
int min( int a, int b )
return ( a < b ) ? a : b;

// minimum for longs
long min( long a, long b )
return ( a < b ) ? a : b;

// minimum for chars
char min( char a, char b )
return ( a < b ) ? a : b;

//etc...

By using templates, you can reduce this duplication to a single function template:

template <class T> T min( T a, T b )
return ( a < b ) ? a : b;

Templates can significantly reduce source code size and increase code flexibility without reducing type safety.

public/private/protected

This facility must be used wisely. Consider the following code:

class Point3D
{
  public:
    float& GetX() { return x; }
    const float& GetX() const { return x; }
    float& GetY() { return y; }
    const float& GetY() const { return y; }
    float& GetZ() { return z; }
    const float& GetZ() const { return z; }

  private:
    float x, y, z;
};

Making accessors for a basic structure like this provides no benefit. Making the accessors inline will make their use as fast as direct access. But then the class would have to be used as follows:

Point3D p1, p2;
float dist = sqrt(
  p1.GetX()*p2.GetX() +
  p1.GetY()*p2.GetY() +
  p1.GetZ()*p2.GetZ());

instead of:

float dist = sqrt(p1.x*p2.x + p1.y*p2.y + p1.z*p2.z);

The only time protected data should be used is when is when you donÆt want the users of the class to either know about the data or modify the data directly.

C++ in games

Engine Design and Architecture

The purpose of these notes is to describe the main design choices that were made in the implementation and the reasons for them.
Engine architecture design factors are motivated by many requirements. The predominant ones are efficiency of image generation, quality of image generation and the provision of a development platform.
The example engine is based on BSP technology. Although BSP management was originally developed for first person shooter games it is a general and versatile scene management methodology and will admit many applications as the examples will demonstrate. Different applications will use different partitioning methods to build the BSP object and these options are embedded in the plug-in that converts the model into the tree.
The implemented applications are examples of common games genres and include:
1st person shooter game (ship and walking person)
car racing and pursuit game
three-dimensional strategy game
BSP technology is also versatile enough to facilitate combinations of the scene types that support these games. These are:

The applicability of BSP technology depends, of course, on the nature of the game application. A popular genre - football games - may or may not be beneficially implemented as a BSP tree depending on the relative complexity of the landscape v. the players. If the stadium is complex then the landscape approach can be used. If, however, the complexity of the 22 players - dynamic objects - is much greater than the landscape then BSP technology will not be optimal.

In general BSP technology is best in applications with large complex static meshes and its inherent advantages reduce as the number and complexity of the dynamic objects increases. It is also supreme in applications which require collision detection with the static mesh.

Game development is facilitated by building the environment or level using a modeling utility or editor then writing plug-ins to define different behaviors, both visual and dynamic for the game objects. Ideally new plug-ins should be developed to create a unique game but several sample plug-ins are given which can be edited (source code) or have their behavior altered by changing the parameter values.
The normal advantages of plug-in pertain. The front-end has no knowledge of the nature of the plug-ins and is not re-compiled when new plug-ins are added. It sees the plug-ins as a group of BSP objects which are new classes, defined by the user, and derived from the BSP base class.
Dynamic objects are handled with their reference position defaulting to particle behavior.

Fly3D Software Architecture

The design reflects the motivation of ease of game development in two main ways. First all behaviors are embedded in plug-ins. This means that the engine and front-ends are separate and distinct from the game application. The engine becomes a tool used by the game developer who needs to know little about the detailed engine behavior (precise ways in which scene management or collision detection, for example, is implemented).

Second inheritance in C++ is heavily exploited. The engine only knows about bsp_objects and many new classes can easily be created in a plug-in that inherits the bsp_object properties and behaviors. Many different objects, that share a common behavior, can be implemented as a base object which specifies the behavior once only.
The flyEngine is a .lib file that needs to be to be linked with all front-ends, plug-ins and utilities. It includes a global pointer where all access to the engine should be directed:

flyEngine *flyengine;

The classes defined in the Fly3D.dll file are:

class vector; // standard x,y,z 3D vector
class mat4x4; // 4x4 matrix to represent rotations, translations and scales
class boundbox; // axis aligned bounding box
class plane; // 3D plane
class vertex; // 3D vertex
class face; // 3D face
class local_system; // local system defined by 3 axis perpendicular vectors
class base_object; // the base class for all objects to derive from
class sound; // raw sound data
class mesh; // 3D mesh with faces, vertices ...
class anim_mesh; // animated 3D mesh with 3D vertex morphing
class stripfan_mesh; // mesh specified as strips and fans
class bezier_curve; // Bezier curve with any dimension
class bezier_patch; // Bezier surface made of several patches
class particle; // point particle in 3D space
class bsp_node; // node from the bsp tree
class bsp_object; // object inside one bsp node (all plug-ins derive from this class)
class static_mesh; // object containing faces in a leaf bsp node (derived from bsp_object)
class light_map; // bitmap for caching light
class light_map_pic; // collection of bitmaps for caching light
class light_vertex; // dynamic vertex light info
class class_desc; // describe fly plug-in classes
class flydll; // handles one dll plug-in
class flydllgroup; // group of fly plug-ins
class directX; // DirectX wrapper
class picture; // a 24 or 32 bits/pixel bitmap
class console; // console with text input and command interpreter
class flyEngine; // the main interface

The engine class includes all the scene objects, textures, models, sounds and curves. It is the main interface with several functions which interact with the simulation data.
One fly level or scene(.fly files) is composed of the following items all included in the flyEngine class:
a BSP tree (BSP nodes, faces, vertices) representing the static scene or level

// vertex array for static bsp faces
int nvert;
vector *vert;

// faces array for static bsp
int nfaces;
face *faces;

// the bsp tree
bsp_node *bsp;

an array of pictures to be used as texture maps

int npiclib;
picture *piclib[MAX_PICTURES];

object meshes, sounds and curves stored as linked lists

// linked list of model objects
mesh *model_obj0;

// linked list of sound objects
sound *sound_obj0;

// linked list of curve objects
bezier_curve *bezier_curve0;

the group of plug-ins used

// group of active plug-ins
flydllgroup dll;

a linked list of stock bsp objects. These are objects that can be cloned and activated during the execution of the game. Activation means adding the object to the BSP tree and to the list of active objects.

// linked list of stock script objects
bsp_object *stock_obj0;

a linked list of active bsp objects.

// linked list of active objects
bsp_object *active_obj0;

Plug-ins

The Fly3D plug-ins are dll files that defines new classes derived from the engine bsp_object class. In this way we can create a dll that implements a new bsp_object to be used in the simulation. The new object will be able to give new behaviour to new virtual functions defined by the bsp_obejct that requires different behavior. Common re-implemented functions are the draw, step and ray_intersect virtual functions.

The bsp_object class is derived from a particle, and so it behaves as a particle. It includes several properties that are common to all plug-ins like position, vel, force, mass, bump, friction and radius. If no custom behavior is defined in new implementations for the object virtual step function, it will move and collide as a particle, using its current vel and force parameters.

The following plug-ins are supplied:

gamelib.dll

lights.dll

bezobj.dll

volfog.dll

panorama.dll

viewport.dll

subdivsurf.dll

cartoon.dll

weapon.dll

gun can include any of the other objects in the dll. It is a container for the projectiles and includes parameters for different behaviour

menu.dll

ship.dll

walk.dll

car.dll

One Fly3D plug-in can implement any number of bsp_objects derived classes. Each class can include any number of custom properties. The properties can be of predefined types like int, float, vector, color, string, picture, mesh, sound, curve, pointer to another stock or active bsp_object etc.

Plug-ins may draw, enumerate objects or do both. The set of draw plug-ins effectively draw into depth-ordered layers which are composited into the frame buffer. In the above exam0ple the first - panorama.dll - draws the backgrounds and enumerates no objects. The object, however, is not inserted into the BSP tree (because it is the background. Gamelib.dll draws nothing but enumerates several objects (light, sound, etc.). The third plug-in draws the rendered view from the player and implements the player object. The final one enumerates an object and draws it as a two-dimensional object directly onto the screen. This object is not inserted into the BSP tree.

The advantage of this organization is the ability to easily define (and clone game) objects by defining their update and draw functions. From the rendering point of view it effectively combines a BSP rendering strategy with a layered approach. The plug-ins are associated with the layers which can be blended into the frame buffer using different blend modes.

As an example of a game action element consider the plug-in dependency for the following case. We require a missile that has sound fixed to it, whose exhaust paints light on nearby objects as it travels and explodes using a particle explosion also with the appropriate sound. The missile is also to emit smoke. The light plug-in instance used by the missile causes the light map(s) within the sphere of influence of the missile light to be recomputed. Sound is fixed to the missile and travels with it. Smoke is modeled and emitted by the travelling missile by simulation in a particle system. The missile æcarriesÆ the explosion and when it is destroyed, this explosion is cloned into the missile position in the BSP tree - effectively replacing it. The activation of the explosion may itself use sound instances and an animated mesh.

bsp_object virtual functions

The bsp_object class defines several virtual functions that should be re-implemented in order to define your own behaviors. If these are not implemented then the object will behave as a particle.

virtual void init();

This function is called for all objects after scene loading is completed. At this point you should initialize all data that your object requires.

virtual int step(int dt);

dt is the elapsed time from the last frame and your object should be updated using this time.
If the object has moved in this time you must return true. Returning 0 will not reposition the object in the BSP tree.

virtual bsp_object *clone();

The clone function must return a new instance of the object with the same parameter settings as the original. It is used to duplicate objects.

virtual mesh *get_mesh();

If the object has a mesh representation then this function should return its current mesh object. If a mesh is returned by this function then drawing, ray intersection and collision will be handled internally.

virtual void draw();

You must draw the object mesh representation and this function is called by the BSP draw for all objects within the view frustum and which have not been culled by the PVS. If not implemented a get_mesh will be called and if it returns a mesh that mesh will be drawn, otherwise no drawing will take place for this object.

virtual mesh *ray_intersect(vector& ro,vector& rd,vector& ip,float& dist,int &facenum,float rad=0.0f);

For special objects that require special ray intersection routines. If not implemented a get_mesh will be called and if it returns a mesh the ray intersection will be done with the mesh. Otherwise no collision detection will occur.

virtual int get_custom_param_desc(int i,param_desc *pd);

Here you should return the description of all external parameters that your object implements.

virtual int message(vector& p,float rad,int msg,int param,void *data);

This is a general object message procedure. Here you should process all messages directed to the object; for example, dynamic illumination or any other custom game message.

Fly3D Front-ends

Front-ends are applications created using the flyengine lib that open a scene and loop through the simulation. The fly.exe, flyEditor.exe, fly.ocx and fluserv.exe are all examples of Fly3D front-ends. They are responsible for creating and initializing a render if needed (the server front-end is an example of a frontend without a render) and loading the plug-ins

The main loop in a front-end is as follows:

// compute elapsed time since last frame in ms (dt)
T1=current time
dt=T1-T0
if dt < 10 return
T0=T1
if dt>1000 return
// simulate dt ms
call flyengine->step(dt)

dt is the time interval for the next frame and in the step function, the following order of operations are performed:

// get input state (keyboard and mouse)
directx->get_input();

// restores all light maps changed in last frame
for each active object
  if object life is < 0
    destroy object removing it from BSP tree and active object list
  elsecall object->step(dt)
if this returns true reposition object in BSP tree as it may have changed nodes

// update all light maps that were changed in this frame (this operation, of course, is
// necessary only for hardware assisted rendering because the maps have been cached into the
// hardware

// send a message to all the plug-ins informing them of the sumulation step

dll.send_message(FLYM_UPDATESCENE,dt,0);

// if in multiplayer mode, process all multiplayer messages

// messages like connect/disconnect and server quit are processed by the engine and

// custom multiplayer messages are passed on to the plug-ins

check_multiplayer();

If we are in server mode, no render is defined so no input and light map operations will be executed.
It is apparent in the above that there is no reference to any rendering activity. BSP rendering occurs when the plug-ins process the update scene message. Some may effect a render and others may not. For example weapon.dll effects no render. Ship.dll on the other hand effects a (BSP) render of the entire scene using the current ship position as the camera position. Another example is viewport.dll which can implement, for example, a back facing camera or a missile camera. This selects a small viewpoint, sets the camera to the required position and effects a BSP render.

Collision Detection

The following functions from the flyEngine should be used for collision detection:

int collision_bsp(bsp_node *n,vector& p1,vector& p2,int elemtype=0,float rad=0.0f);
int collision_test(bsp_node *n,vector& p1,vector& p2,int elemtype=0,float rad=0.0f);

The collision_test returns 0 if no collision is found between p1 and p2 and 1 if a collision is found (not the closest collision). The collision_bsp returns the closest collision from p1 to p2.

If the elem type is specified, only objects with the specified type will be intersected (-1 for only the BSP faces). If radius is >0, the collision will be tested for a sphere with the specified radius.

The distinction of usage between a nearest collision and the detection of any collision is as follows. A moving object always needs the nearest collision for correct impact calculation. On the other hand a robot tracking a player simply needs to know if the player is still visible from his viewpoint in which case an æanyÆ collision will suffice.Information about the collision is returned in the flyEngine class members:

// ray intersection data
bsp_object *hitobj; // bsp_object intersected
mesh *hitmesh; // mesh from the bsp_objects intersected
int hitface; // face from mesh intersected
vector hitip; // intersection point in global co-ordinates

Stock and Active object list

All objects defined in a .fly file are loaded into the stock objects linked list when a open_fly_file command is executed. When saving a .fly file, all object properties in the stock objects the liked list are saved to the file.

Any stock object can be activated (cloned and added to the bsp) using the following command from the flyEngine class:

void activate(bsp_object *stockobj,int flag=1);

This will make a clone of the stock object (using the clone() bsp_object virtual function) and add it to the end of the active object linked list. If flag is true, the object is also added to the bsp.
All objects in the active object linked list will have its step() function called for every frame and may be selected for drawing if in the current view frustum and not culled by the pvs.

Sphere of Influence

Spheres of influence are used for many operation in the engine. It allows fast selection of all objects in a sphere positioned anywhere in the bsp. A callback function is used, and a call is made for each bsp_object included inside the specified sphere of influence.

// recourse bsp calling a custom calback function
void apply_bsp(bsp_node *n,vector& p,float rad,void *data,void (*func)(void *data,bsp_object *e));

The callback function should be something like this:

void apply_bsp_callback(void *data,bsp_object *e)
{
// ...
}

Communicating between objects and plug-ins

Plug-ins are groups of objects and there are 2 types of messages used for communication between them - object messages and plug-in messages. Object messages are processed in the message() bsp_object virtual function. Plug-in messages are processed in the dll exported fly_message() function.
The following functions are used for sending messages:

void flyEngine::send_bsp_message(bsp_node *n,vector& p,float rad,int msg,int param,void *data);
int flydll::send_message(int msg,int param,void *data);

The send_message() function sends the message to all plug-in dll in the current scene calling the fly_message() exported function of all dlls. The send_bsp_message() send a message to all bsp_object derived classes included in the specified sphere of influence.

Camera and Scene Drawing

Each plug-in dll will receive a FLYM_UPDATESCENE message every frame. At this point it might draw over the frame buffer if needed. Usually, one plug-in will draw the scene from the players perspective, but other plug-ins like the viewport may also draw the scene from other objects perspectives for implementing missile cameras, and rear cameras. The code will be:

flyengine->set_camera(camobj);
flyengine->draw_bsp();

Text Output

The following functions are used for text output. When entering text mode, a 2D mode is selected for the render. This way we can use 2D calls to draw text textured faces to represent the letters.

// text output functions
void start_text_mode(); // sets 2D mode
void end_text_mode(); // ends 2D mode
void draw_text(int x,int y,char *text); // draw text
void draw_text_center(int x,int y,char *text); // draw centered text
void set_status_msg(char *fmt, ...); // outputs to console

Drawing the status of a game, for example, would be as follows:

flyengine->start_text_mode();

char str[64];
sprintf(str,"FPS:%i N:%i", flyengine->frame_rate,flyengine->nodedrawcount);
flyengine->draw_text( 0,0, str );

flyengine->end_text_mode();

Rendering API

The source code has been written using both OpenGL. All rendering is done inside the object draw() virtual function as already explained. New objects created on plug-ins should implement the bsp_object function draw(). In it you must place the appropriate calls required for drawing your object. A simple example for drawing a mesh inside a newly created object would be:

void my_object::draw()
{
  // Draw with OpenGL

  glPushMatrix();
  glTranslatef(pos.x,pos.y,pos.z);

  glBlendFunc(GL_ONE, GL_ONE);
  my_mesh->draw();
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glPopMatrix();
}

A detailed treatment of programming in OpenGL is outside the scope of this text. A summary of each set of operations in the code above is:

Setting the current rendering matrix to position the object in global co-ordinates

glPushMatrix();
glTranslatef(pos.x,pos.y,pos.z);

Set the blending mode required for this object:

glBlendFunc(GL_ONE, GL_ONE);

Draw the object mesh

my_mesh->draw();

Restore the blending mode

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Restore rendering matrix

glPopMatrix();